package fidel.spring.shiro.demo.service.impl;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.util.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;

import fidel.spring.shiro.demo.exception.CustomException;
import fidel.spring.shiro.demo.mapper.SysPermissionMapper;
import fidel.spring.shiro.demo.mapper.SysUserMapper;
import fidel.spring.shiro.demo.po.ActiveUser;
import fidel.spring.shiro.demo.po.SysPermission;
import fidel.spring.shiro.demo.po.SysUser;
import fidel.spring.shiro.demo.service.SysService;

/**
 * 
 * 认证和授权的服务接口实现
 * 
 * @version 1.0
 */
public class SysServiceImpl implements SysService {

    @Autowired
    private SysUserMapper sysUserMapper;

    @Autowired
    private SysPermissionMapper sysPermissionMapper;

    /**
     * 使用令牌的usercode和password查询对应的唯一用户
     * <p>
     * 认证过程：根据用户身份（账号）查询数据库
     * <p>
     * 如果查询不到，则抛出异常
     * <p>
     * 对输入的密码和数据库密码进行比对，如果一致则认证通过
     * 
     * @param usercode 在认证过程中提交的账户身份
     * @param password 在认证过程中提交的账户明文密码
     * @throws NoSuchAlgorithmException
     * @throws CustomException
     */
    @Override
    public SysUser authenticate(AuthenticationToken token) throws NoSuchAlgorithmException, CustomException {
        SysUser sysUser = null;
        String usercode = (String) token.getPrincipal();
        String password = String.valueOf((char[]) token.getCredentials());
        if (!StringUtils.hasText(usercode)) {
            throw new CustomException("提交的用户账号为空");
        }
        if (!StringUtils.hasText(password)) {
            throw new CustomException("提交的用户密码为空");
        }
        // 根据用户账号查询数据库
        List<SysUser> list = sysUserMapper.selectByUsercode(usercode);
        if (list.size() > 1) {
            throw new CustomException("查询得到的用户不唯一");
        }
        if (list.size() == 1) {
            sysUser = list.get(0);
            verifyPassword(sysUser, password);
            return sysUser;
        }
        throw new UnknownAccountException("用户账号不存在");
    }

    /**
     * 验证SysUser实例的密码散列是否是当前实例的盐与给定的明文密码构成的散列值
     * 
     * @param sysUser  应当包含散列与盐的SysUser实例
     * @param password 在认证过程中提交的账户明文密码
     * @return 如果SysUser实例的密码散列等于盐与明文密码构成的散列，则返回散列值；否则返回null
     */
    private void verifyPassword(SysUser sysUser, String password) throws NoSuchAlgorithmException {
        // 获取从数据库中查询得到的SysUser实例的密码散列
        String hash = sysUser.getPassword();
        // 获取盐
        String salt = sysUser.getSalt();
        StringBuffer hexStringBuffer = new StringBuffer();
        byte[] bytes = String.valueOf(salt.concat(password)).getBytes();
        // 以md5散列算法构建实现该算法的MessageDigest对象
        MessageDigest messageDigest = MessageDigest.getInstance("md5");
        // 通过执行最后的操作（如填充）来完成散列计算
        byte[] hashBytes = messageDigest.digest(bytes);
        for (int i = 0; i < hashBytes.length; i++) {
            if ((0xff & hashBytes[i]) < 0x10) {
                // toHexString：返回整数参数的字符串表示，是一个以16为基数的无符号整数
                hexStringBuffer.append("0".concat(Integer.toHexString((0xff & hashBytes[i]))));
            } else {
                hexStringBuffer.append(Integer.toHexString(0xff & hashBytes[i]));
            }
        }
        /*
         * 将盐与密码的组合串进行散列后，同数据库密码进行比对
         * 如果一致，则说明认证通过
         * 否则抛出异常
         */
        String generatedHash = hexStringBuffer.toString();
        if (!generatedHash.equalsIgnoreCase(hash)) {
            throw new IncorrectCredentialsException("用户账号对应的密码错误");
        }
    }

    /**
     * 授权
     */
    @Override
    public void authorize(ActiveUser activeUser) {
        // 得到用户id
        String userid = activeUser.getUserid();
        // 根据用户id查询菜单
        List<SysPermission> menus = sysPermissionMapper.selectMenusByUserid(userid);
        // 根据用户id查询按钮
        List<SysPermission> buttons = sysPermissionMapper.selectButtonsByUserid(userid);
        // 放入权限范围的菜单和uri
        activeUser.setMenus(menus);
        activeUser.setButtons(buttons);
    }

}
